using System;

namespace SharpMud.Net
{
	/// <summary>
	/// Represents a MUD-mode packet - which is basically an arbritrary collection of bytes with
    /// a header that indicates the count of bytes in Big Endian, unsigned Int32, notation.
	/// </summary>
    public class MudModePacket : IDataPacket
	{
		private byte[] _Contents;

        /// <summary>
        /// 
        /// </summary>
		public MudModePacket()
			:this(new byte[] {})
		{
		}

        /// <summary>
        /// 
        /// </summary>
        /// <param name="contents"></param>
		public MudModePacket(byte[] contents)
		{
			_Contents = contents;
		}

        /// <summary>
        /// 
        /// </summary>
		public long TotalPacketSize
		{
			get
			{
				return (long)4 + _Contents.LongLength;
			}
		}

        /// <summary>
        /// Gets the four bytes that represent the header for the MUD-Mode packet, in the ready-to-send order
        /// </summary>
		public byte[] GetHeader()
		{
			System.Collections.ArrayList al = new System.Collections.ArrayList(System.BitConverter.GetBytes((UInt32)_Contents.Length));
			if(System.BitConverter.IsLittleEndian)
			{	al.Reverse();									}
			else
			{	//No bytes to reverse, already in right order	
			}
			return (byte[])al.ToArray(typeof(byte));		
		}

		/// <summary>
		/// Gets the contents of the MUD-Mode packet
		/// </summary>
        public byte[] GetContents()
		{
			return _Contents;
		}

        /// <summary>
        /// Sets the contents of the MUD-Mode packet - completely arbritrary
        /// </summary>
		public void SetContents(byte[] value)
		{
			if(value==null) throw new ArgumentNullException("value");
			_Contents = value;
		}
		
		#region IDataPacket Members

		/// <summary>
		/// Converts the entire MUD-Mode packet to an array of bytes
		/// </summary>
        public byte[] ToByteArray()
		{
			byte[] bs = new byte[4 + _Contents.Length];
			GetHeader().CopyTo(bs,0);
			GetContents().CopyTo(bs,4);
			return bs;
		}

		#endregion

        /// <summary>
        /// Attempts to take a presumably full MUD-mode packet and parse it
        /// </summary>
        public static MudModePacket Parse(byte[] data)
        {
            if (data == null)
                throw new ArgumentNullException("data");
            if (data.Length < 4)
                throw new InvalidPacketException("Not enough data was provided to indicate a valid MUD-Mode packet.");
            byte[] header = new byte[4];
            byte[] body; // Not assinged until the amount of data is known
            System.Array.Copy(data, 0, header, 0, 4);
            UInt32 expectedLength;
            if (BitConverter.IsLittleEndian)
            {
                System.Collections.Generic.List<byte> btemp = new System.Collections.Generic.List<byte>(header);
                btemp.Reverse();
                expectedLength = BitConverter.ToUInt32(btemp.ToArray(), 0);
            }
            else
            {
                expectedLength = BitConverter.ToUInt32(header, 0);
            }

            if (((UInt32)(data.Length - 4)) == expectedLength)
            {
                body = new byte[(long)expectedLength];
                System.Array.Copy(data, 4, body, 0, data.Length - 4);
                return new MudModePacket(body);   
            }
            else if (((UInt32)(data.Length - 4)) > expectedLength)
            {
                throw new InvalidPacketException("There was too much data present based on what was indicated by the MUD-Mode packet header.");
            }
            else // if(((UInt32)(data.Length - 4)) < expectedLength)
            {
                throw new InvalidPacketException("There was not enough data present based on what was indicated by the MUD-Mode packet header.");
            }
        }

        /// <summary>
        /// Takes a set of bytes and determines how many bytes are left before the packet is ready for parsing. If the four bytes for the header are not yet presented, it will return the number of bytes needed for the header. Otherwise, it will return the number of bytes needed for the rest of the packet.
        /// </summary>
        public static UInt32 TryParse(byte[] data, int index, out MudModePacket result, out byte[] remainderData)
        {
            if (data == null)
                throw new ArgumentNullException("data");
            if ((int)(data.Length) < (int)4)
            {
                result = null;
                remainderData = null;
                return ((UInt32)4) - ((UInt32)(data.Length));
            }
            else
            {
                byte[] header = new byte[4];
                System.Array.Copy(data, 0, header, 0, 4);
                UInt32 expectedLength;
                if (BitConverter.IsLittleEndian)
                {
                    System.Collections.Generic.List<byte> btemp = new System.Collections.Generic.List<byte>(header);
                    btemp.Reverse();
                    expectedLength = BitConverter.ToUInt32(btemp.ToArray(), 0);
                }
                else
                {
                    expectedLength = BitConverter.ToUInt32(header, 0);
                }

                if (((UInt32)(data.Length - 4)) < expectedLength)
                {
                    result = null;
                    remainderData = null;
                    return (expectedLength) - ((UInt32)(data.Length - 4));
                }
                else if( ((UInt32)(data.Length - 4)) == expectedLength )
                {
                    byte[] dataContents = new byte[data.Length-4];
                    data.CopyTo(dataContents, 4);
                    result = new MudModePacket(dataContents);
                    remainderData = new byte[] { };
                    return 0;
                }
                else
                {
                    byte[] dataContents = new byte[data.Length - 4];
                    remainderData = new byte[(data.Length - 4) - (int)expectedLength];
                    data.CopyTo(dataContents, 4);
                    data.CopyTo(remainderData, 4 + (int)expectedLength);
                    result = new MudModePacket(dataContents);
                    return 0;
                }
            }
        }

        

        /// <summary>
        /// 
        /// </summary>
        public sealed class InvalidPacketException : System.Exception
        {
            private const string baseMessage = "There was a problem parsing the given MUD-Mode packet.";
            private const string baseMessageOPEN = "There was a problem parsing the given MUD-Mode packet:";

            /// <summary>
            /// 
            /// </summary>
            public InvalidPacketException()
                : base(baseMessage)
            {
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="additionalInfo"></param>
            public InvalidPacketException(string additionalInfo)
                :base(baseMessageOPEN.Trim() + " " + additionalInfo.Trim())
            {
            }
        }
	}
}
